using System;
using System.Collections.Generic;
using System.Text;
using RS.DOM.Common;
using RS.DOM.Construction;
using RS.DOM.Types;

namespace RS.DOM.Construction
{
    public class ExpressionCollection : List<Expression>
    {
        public void Build()
        {
            foreach (Expression expression in this)
            {
                expression.Build();
            }
        }
    }

    public class Expression : Line
    {
        #region Constructors

        public Expression(Context context)
            : base(context)
        {
            this.Context = new Context(this.Context.Document, this.Context.Group, this);
            m_description = string.Empty;
        }

        #endregion

        #region Members

        private const int INCREMENT_BRACKET_STEP = 2;
        private string m_description;

        #endregion

        #region Properties

        public string Description
        {
            get
            {
                return m_description;
            }

            set
            {
                m_description = value;
            }
        }

        #endregion

        #region Methods

        public Expression GetBracketPriorityExpression()
        {
            Expression line = new Expression(this.Context);
            int bracketPriority = 0;
            foreach (Token token in this)
            {
                if (bracketPriority < token.BracketPriority)
                {
                    bracketPriority = token.BracketPriority;
                    line.Clear();
                }
                else if (bracketPriority > token.BracketPriority)
                {
                    break;
                }

                if (bracketPriority == token.BracketPriority)
                {
                    line.Add(token);
                }
            }

            return line;
        }

        public Expression GetSquareBracketPriorityExpression()
        {
            Expression line = new Expression(this.Context);
            int squareBracketPriority = 0;
            foreach (Token token in this)
            {
                if (squareBracketPriority < token.SquareBracketPriority)
                {
                    squareBracketPriority = token.SquareBracketPriority;
                    line.Clear();
                }
                else if (squareBracketPriority > token.SquareBracketPriority)
                {
                    break;
                }

                if (squareBracketPriority == token.SquareBracketPriority)
                {
                    line.Add(token);
                }
            }

            return line;
        }

        public Expression GetPriorityOperatorExpression()
        {
            Expression line = new Expression(this.Context);
            Token priorityToken = null;
            int priority = 0;

            foreach (Token token in this)
            {
                if (priority < token.Priority &&
                    token.TokenType == TokenType.Operator)
                {
                    priority = token.Priority;
                    priorityToken = token;
                }
            }

            line.Add(priorityToken.PrevToken);
            line.Add(priorityToken);
            line.Add(priorityToken.NextToken);

            return line;
        }

        public Expression GetUnarOperatorExpression()
        {
            Expression line = new Expression(this.Context);

            foreach (Token token in this)
            {
                if (token.TokenType != TokenType.Operator)
                {
                    if (token.PrevToken != null && token.PrevToken.TokenType == TokenType.Operator)
                    {
                        Token unarOperator = token.PrevToken;

                        if (unarOperator.PrevToken == null ||
                            unarOperator.PrevToken.TokenType == TokenType.None ||
                            unarOperator.PrevToken.TokenType == TokenType.Operator ||
                            unarOperator.BracketPriority > unarOperator.PrevToken.BracketPriority ||
                            unarOperator.SquareBracketPriority > unarOperator.PrevToken.SquareBracketPriority)
                        {
                            line.Add(unarOperator);
                            line.Add(token);
                            break;
                        }
                    }
                }
            }

            return line;
        }

        private bool FindOperation(OperationType[] operations, OperationType operation)
        {
            foreach (OperationType currOperation in operations)
                if (operation == currOperation)
                    return true;

            return false;
        }

        public override void Build()
        {
            //priority
            OperationType[] priority9 = new OperationType[] { OperationType.Generate, OperationType.Dimension };
            OperationType[] priority8 = new OperationType[] { OperationType.Agregate, OperationType.Point, OperationType.Add, OperationType.Rational };
            OperationType[] priority7 = new OperationType[] { OperationType.Multiply, OperationType.Devide, OperationType.DevideWithRest };
            OperationType[] priority6 = new OperationType[] { OperationType.Plus, OperationType.Subtract };
            OperationType[] priority5 = new OperationType[] { OperationType.Equal, OperationType.Great, OperationType.Less, OperationType.LessEqual };
            OperationType[] priority4 = new OperationType[] { OperationType.And, OperationType.Or };
            OperationType[] priority3 = new OperationType[] { OperationType.Apply, OperationType.PlusApply, OperationType.DoubleApply, OperationType.AddApply };
            OperationType[] priority2 = new OperationType[] { OperationType.Compare, OperationType.NotNull };
            OperationType[] priority1 = new OperationType[] { OperationType.Next };

            foreach (Token token in this)
            {
                if (token.TokenType == TokenType.Operator)
                {
                    if (this.FindOperation(priority9, token.OperationType))
                        token.Priority = 10;
                    else if (this.FindOperation(priority8, token.OperationType))
                        token.Priority = 9;
                    else if (this.FindOperation(priority7, token.OperationType))
                        token.Priority = 8;
                    else if (this.FindOperation(priority6, token.OperationType))
                        token.Priority = 7;
                    else if (this.FindOperation(priority5, token.OperationType))
                        token.Priority = 6;
                    else if (this.FindOperation(priority4, token.OperationType))
                        token.Priority = 5;
                    else if (this.FindOperation(priority3, token.OperationType))
                        token.Priority = 4;
                    else if (this.FindOperation(priority2, token.OperationType))
                        token.Priority = 3;
                    else if (this.FindOperation(priority1, token.OperationType))
                        token.Priority = 2;
                    else
                        token.Priority = 1;
                }
            }

            //bracket priority
            int priority = 1;
            foreach (Token token in this)
            {
                if (token.Text == "(")
                {
                    priority += INCREMENT_BRACKET_STEP;
                }
                else if (token.Text == ")")
                {
                    priority -= INCREMENT_BRACKET_STEP;
                }
                else if (token.OperationType != OperationType.Compare &&
                         token.OperationType != OperationType.NotNull)
                {
                    token.BracketPriority = priority;
                }
                else
                {
                    token.BracketPriority = priority - 1;
                }
            }

            //square bracket priority
            priority = 1;
            foreach (Token token in this)
            {
                if (token.Text == "[")
                    priority += INCREMENT_BRACKET_STEP;

                token.SquareBracketPriority = priority;

                if (token.Text == "]")
                    priority -= INCREMENT_BRACKET_STEP;
            }
        }

        private void RemoveBrackets()
        {
            int i = 0;
            while (i < this.Count)
            {
                if (this[i].TokenType == TokenType.Bracket)
                {
                    this.Remove(this[i]);
                }
                else
                {
                    i++;
                }
            }
        }

        public Expression Copy()
        {
            Expression line = new Expression(this.Context);
            foreach (Token token in this)
            {
                Token newToken = token.Copy();
                line.Add(newToken);
            }

            return line;
        }

        public void ReplaceMutable(string text, Token token)
        {
            int i = 0;
            while (i < this.Count)
            {
                if (this[i].Text == text)
                {
                    this.RemoveRange(i, 1);

                    Token newToken = token.Copy();
                    this.Insert(i, newToken);
                }
                else if (this[i].TokenType == TokenType.SubExpression)
                {
                    ((SubExpression)this[i]).Result.ReplaceMutable(text, token);
                }

                i++;
            }
        }

        public override Token Execute()
        {
            string log = this.ToString();
            this.Context.Document.AddLog(log);

            this.CollapseSquareBrackets();

            //get mutable tokens
            MutableCollection mutables = new MutableCollection();
            foreach (Token token in this)
            {
                if (token.TokenType == TokenType.Mutable ||
                    token.TokenType == TokenType.Name)
                {
                    Token mutable = token.Resolve();
                    if(mutable.TokenType == TokenType.Mutable)
                    {
                        if(!mutables.Contains(mutable.Text))
                            mutables.Add((Mutable)mutable);
                    }
                }
            }

            if (mutables.Count > 0)
            {
                ResultSet result = new ResultSet(this.Context);
                ExpressionCollection expressions = new ExpressionCollection();

                //all combinations mutables
                int digit = mutables.Count - 1;
                mutables[digit].SelectedIndex = -1;

                while (digit >= 0)
                {
                    mutables[digit].SelectedIndex += 1;
                    if (mutables[digit].SelectedIndex >= mutables[digit].Result.Count)
                    {
                        mutables[digit].SelectedIndex = 0;
                        digit -= 1;
                        continue;
                    }
                    else
                    {
                        digit = mutables.Count - 1;
                    }

                    //replace mutable tokens
                    Expression expression = this.Copy();
                    foreach (Mutable mutable in mutables)
                    {
                        expression.Description += (mutable.Text + "=" + mutable.SelectedToken.Result.ToString() + ";");
                        expression.ReplaceMutable(mutable.Text, mutable.SelectedToken);
                    }

                    expressions.Add(expression);
                }

                //execute all expressions
                foreach (Expression expression in expressions)
                {
                    Token token = expression.BuildAndExecute();
                    result.Result.Add(token);
                }

                return result;
            }
            else
            {
                return ExecuteEpression();
            }
        }

        private Token ExecuteEpression()
        {
            this.Context.Document.AddLog(this.Description);

            if (this.Count > 1)
            {
                this.RemoveBrackets();

                this.CollapseBrackets();

                return this.ExecuteOperators();
            }
            else
            {
                return this[0].Resolve();
            }
        }

        private void CollapseSquareBrackets()
        {
            Expression line = this.GetSquareBracketPriorityExpression();

            while (this.Count != line.Count)
            {
                SubExpression token = new SubExpression(this.Context, line);

                token.BracketPriority = line.FirstToken.BracketPriority - 1;

                this.Replace(line, token);

                line = this.GetSquareBracketPriorityExpression();
            }
        }

        private void CollapseBrackets()
        {
            Expression line = this.GetBracketPriorityExpression();

            while (this.Count != line.Count)
            {
                SubExpression token = new SubExpression(this.Context, line);

                token.BracketPriority = line.FirstToken.BracketPriority - 1;

                this.Replace(line, token);

                line = this.GetBracketPriorityExpression();
            }
        }

        private Token ExecuteOperators()
        {
            Expression expression = this.Copy();
            try
            {
                while (expression.Count > 1)
                {
                    //this.Context.Document.AddLog(expression.ToString());
                    Expression line = null;

                    //unar operator
                    line = expression.GetUnarOperatorExpression();

                    if (line.Count > 0)
                    {
                        Token operation = line.GetOperation();
                        Token token = line.ExecuteUnarOperator(operation);
                        
                        token.BracketPriority = line.FirstToken.BracketPriority;
                        token.SquareBracketPriority = line.FirstToken.SquareBracketPriority;

                        expression.Replace(line, token);

                        continue;
                    }

                    //binary operator
                    line = expression.GetPriorityOperatorExpression();

                    if (line.Count > 0)
                    {
                        Token operation = line.GetOperation();
                        Token token = line.ExecuteOperator(operation);

                        token.BracketPriority = line.FirstToken.BracketPriority;
                        token.SquareBracketPriority = line.FirstToken.SquareBracketPriority;
                        
                        expression.Replace(line, token);

                        continue;
                    }
                }

                return expression[0].Resolve();
            }
            catch (Exception ex)
            {
                throw new Exception("Operators: " + expression.ToString() + "\r\n" + ex.Message);
            }
        }

        private Token ExecuteUnarOperator(Token operation)
        {
            try
            {
                operation.NextToken = operation.NextToken.Resolve();

                //this.Context.Document.AddLog("..." + operation.PrevToken.ToString() + operation.ToString() + operation.NextToken.ToString());

                Token token = operation.NextToken.DoUnarOperation(operation.OperationType);

                //this.Context.Document.AddLog("......" + token.ToString());

                return token;
            }
            catch (Exception ex)
            {
                throw new Exception("Operator: " + operation.ToString() + "\r\nError: " + ex.Message);
            }
        }

        private Token ExecuteOperator(Token operation)
        {
            try
            {
                operation.PrevToken = operation.PrevToken.Resolve();

                //do not resolve second operand
                if (operation.OperationType != OperationType.Compare &&
                    operation.OperationType != OperationType.CompareInNot &&
                    operation.OperationType != OperationType.NotNull &&
                    operation.OperationType != OperationType.Or)
                    operation.NextToken = operation.NextToken.Resolve();

                //this.Context.Document.AddLog("..." + operation.PrevToken.ToString() + operation.ToString() + operation.NextToken.ToString());

                Token token = operation.PrevToken.DoOperation(operation.NextToken, operation.OperationType);

                //this.Context.Document.AddLog("......" + token.ToString());

                return token;
            }
            catch (Exception ex)
            {
                throw new Exception("Operator: " + operation.ToString() + "\r\nError: " + ex.Message);
            }
        }

        #endregion
    }
}
